Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

snmp: initial SNMP driver for power control#251

Merged
bennyz merged 10 commits intojumpstarter-dev:mainfrom
bennyz:snmp-driver
Feb 12, 2025
Merged

snmp: initial SNMP driver for power control#251
bennyz merged 10 commits intojumpstarter-dev:mainfrom
bennyz:snmp-driver

Conversation

@bennyz
Copy link
Copy Markdown
Member

@bennyz bennyz commented Feb 5, 2025

Simple SNMP driver for power management

apiVersion: jumpstarter.dev/v1alpha1
kind: ExporterConfig
endpoint: grpc.jumpstarter.apps.my.cluster.com:443
tls:
  ca: ''
  insecure: true
token: <token>
export:
  power:
    type: "jumpstarter_driver_snmp.driver.SNMPServer"
    config:
      host: "pdu.mgmt.com"
      user: "user"
      plug: 32
      quiescent_period: 3
      oid: "1.2.3.4.4"

Summary by CodeRabbit

  • New Features

    • Introduced a new client interface for SNMP power control, allowing users to manage power states and access a command-line interface.
    • Added support for power cycling functionality through a new method in the SNMP driver.
    • Added a new configuration resource for the SNMP driver with necessary parameters for SNMP communication.
  • Documentation

    • Enhanced documentation with configuration examples and API usage for the SNMP driver, including detailed descriptions of parameters and methods.
    • Included the SNMP driver in the documentation table of contents.
  • Chores

    • Updated project configuration files to include new dependencies and project metadata for the SNMP driver.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 5, 2025

Warning

Rate limit exceeded

@bennyz has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 20 minutes and 12 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 769aa90 and 135610e.

📒 Files selected for processing (1)
  • docs/source/api-reference/drivers/snmp.md (1 hunks)

Walkthrough

This update introduces a new SNMP driver that enables asynchronous power control operations. A new client class (SNMPServerClient) and a driver class (SNMPServer) have been added for SNMP power management with methods to turn power on, off, and operate a command-line interface. The changes include updates to configuration files and documentation, enhancements to the power cycling functionality in the existing PowerClient, as well as tests and import adjustments to support the new features.

Changes

File(s) Change Summary
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py
packages/jumpstarter-driver-snmp/examples/exporter.yaml
Added SNMP power control functionality via a new client (SNMPServerClient) and driver (SNMPServer) with methods for on/off/cli (and cycle via documentation). Unit tests and exporter configuration were also introduced.
packages/jumpstarter-driver-snmp/pyproject.toml
pyproject.toml
Introduced new project metadata for the SNMP driver and added a workspace source entry for jumpstarter-driver-snmp.
packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py
packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py
Updated the PowerClient class by simplifying the on/off methods and adding a new cycle method plus CLI command; tests were adjusted accordingly.
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py Updated import statements, removing an obsolete mock dependency and adding a direct import from the main driver module.
docs/source/api-reference/drivers/snmp.md
docs/source/api-reference/drivers/index.md
Added API documentation for the new SNMP driver and updated the documentation index to include the new file.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant SNMPServerClient
    participant SNMPServer
    participant SNMPEngine
    User->>CLI: Execute "snmp on"/"snmp off" command
    CLI->>SNMPServerClient: Invoke on()/off() method
    SNMPServerClient->>SNMPServer: Forward command (@export on/off)
    SNMPServer->>SNMPEngine: Configure engine & send SNMP set command
    SNMPEngine-->>SNMPServer: Return response (success/failure)
    SNMPServer-->>SNMPServerClient: Pass back status
    SNMPServerClient-->>CLI: Output confirmation
Loading
sequenceDiagram
    participant User
    participant CLI
    participant PowerClient
    User->>CLI: Execute cycle command with wait time
    CLI->>PowerClient: Call cycle(wait)
    PowerClient->>PowerClient: Invoke off() method
    PowerClient->>PowerClient: Wait (sleep for 'wait' seconds)
    PowerClient->>PowerClient: Invoke on() method
    PowerClient-->>CLI: Return cycle completion message
Loading

Possibly related PRs

  • add PR comment with PR images URIs jumpstarter#249: The changes in the main PR, specifically the addition of the SNMPServerClient class and its methods (on, off, cli), are related to the modifications in the retrieved PR, which also introduces a driver class with similar methods (on, off) for power control, indicating a shared functionality in managing power states.
  • NOYITO USB Relay Power Driver jumpstarter#268: Adjusts the return types of the on and off methods in the PowerClient class, which directly relates to the method behavior changes introduced here.

Suggested reviewers

  • mangelajo

Poem

Hop, hop, code in flight,
A rabbit twitches with delight.
SNMP commands jump so high,
Power cycles weave nigh,
Bugs retreat in carrot’s sight.
New drivers bloom with every byte,
Happy hops to a future bright!


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@bennyz
Copy link
Copy Markdown
Member Author

bennyz commented Feb 5, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 5, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py (2)

46-55: Consider a more consistent approach to asynchronous execution.

The _setup_snmp method checks for a running event loop and creates one if missing, but the overall design still seems synchronous in certain places (e.g., in power_cycle). Consider standardizing on an async architecture to avoid mixed paradigms, especially if you plan to integrate with other async code.

🧰 Tools
🪛 GitHub Actions: Run Tests

[error] Failed to spawn: pytest. Caused by: No such file or directory (os error 2)


163-167: Optional cleanup or resource management step might be beneficial.

close() is currently a no-op but does call super().close() if defined. In the event that future changes introduce persistent transports or caches, consider finalizing or cleaning them in this method.

🧰 Tools
🪛 GitHub Actions: Run Tests

[error] Failed to spawn: pytest. Caused by: No such file or directory (os error 2)

packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1)

24-48: Enhance error reporting in CLI commands.

Currently, the CLI subcommands (on, off, cycle) print only the returned result. If unexpected exceptions occur in the SNMP driver, consider capturing and displaying them with more context or exit codes to improve troubleshooting.

🧰 Tools
🪛 GitHub Actions: Run Tests

[error] Failed to spawn: pytest. Caused by: No such file or directory (os error 2)

🛑 Comments failed to post (1)
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py (1)

150-156: 🛠️ Refactor suggestion

Avoid blocking calls inside async workflows.

time.sleep(self.quiescent_period) blocks the entire thread. If asynchronous usage is intended, consider replacing it with await asyncio.sleep(self.quiescent_period) and turning power_cycle into an async function for true non-blocking behavior.

- time.sleep(self.quiescent_period)
+ import asyncio
+ await asyncio.sleep(self.quiescent_period)

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: Run Tests

[error] Failed to spawn: pytest. Caused by: No such file or directory (os error 2)

Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py Outdated
@bennyz
Copy link
Copy Markdown
Member Author

bennyz commented Feb 5, 2025

todo:

  • inherit PowerClient
  • add tests
  • add examples
  • add docs

Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
@mangelajo
Copy link
Copy Markdown
Member

Great work, don't get discouraged by the amount of comments, it's only about standardizing our interfaces as much as possible. Then when you have some implementation it really doesn't matter what is the underlaying power driver or storage system/write method, etc...

Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py Outdated
@bennyz bennyz force-pushed the snmp-driver branch 2 times, most recently from 3d6629c to 4f1fad4 Compare February 6, 2025 15:30
@bennyz bennyz marked this pull request as ready for review February 6, 2025 16:27
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
packages/jumpstarter-driver-snmp/pyproject.toml (2)

12-16: Dependencies Section is Well-Defined

Dependencies for the project are properly declared. The explicit pinning of pysnmp to version 7.1.16 ensures consistency; however, if you intend to permit non-breaking updates, consider using a version specifier like ~=7.1.16 or a minimum version constraint.


18-26: Dev Dependency Version Constraints

Dev dependencies are set up correctly under [dependency-groups]. However, note that constraints such as "pytest-anyio>=0.0.0" and "pytest-asyncio>=0.0.0" are effectively unbounded. Specifying a more meaningful minimum version can help avoid unexpected issues in the future.

packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1)

11-18: Document the inherited cycle() method.

Since PowerClient now provides a cycle() method, consider adding documentation for this inherited functionality to make the API more discoverable.

 class SNMPServerClient(PowerClient):
     """Client interface for SNMP Power Control"""
+
+    The following methods are available:
+    - on(): Turn power on
+    - off(): Turn power off
+    - cycle(quiescent_period: int = 2): Power cycle the device with a configurable wait period
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py (1)

97-125: Add test coverage for error handling scenarios.

The test suite would benefit from additional test cases covering:

  1. Hostname resolution failures
  2. SNMP timeout scenarios
  3. Error responses from the SNMP server

Here's a suggested test case for hostname resolution:

def test_hostname_resolution_failure():
    """Test behavior when hostname resolution fails"""
    with patch('socket.gethostbyname', side_effect=socket.gaierror), \
         pytest.raises(SNMPError, match="Failed to resolve hostname"):
        SNMPServer(
            host="invalid.host",
            user="testuser",
            plug=1
        )
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py (1)

109-123: Avoid hardcoded target names in SNMP configuration.

The target names "my-creds" and "my-target" should be configurable or use more meaningful defaults.

+    target_params_name: str = field(default="snmp-params")
+    target_address_name: str = field(default="snmp-target")
+
     def _setup_snmp(self):
         # ...
         config.add_target_parameters(
             snmp_engine,
-            "my-creds",
+            self.target_params_name,
             self.user,
             security_level
         )
         config.add_target_address(
             snmp_engine,
-            "my-target",
+            self.target_address_name,
             udp.DOMAIN_NAME,
             (self.ip_address, self.port),
-            "my-creds",
+            self.target_params_name,
             timeout=int(self.timeout * 100),
         )
packages/jumpstarter-driver-snmp/examples/exporter.yaml (1)

7-10: TLS and Token Configuration
The TLS block is set to use an insecure connection (with an empty CA), and the token is a placeholder. While this may be acceptable for testing or development, ensure that secure values are used in production environments.

docs/source/api-reference/drivers/snmp.md (2)

25-39: Detailed Parameter Documentation
The table of configuration parameters is well-structured and informative. For added clarity, consider including an additional column with example values or brief usage notes, which could further aid users in understanding how to appropriately set each parameter.


56-64: Cycle Method Documentation
The documentation for cycle(quiescent_period: int = 2) effectively describes the method’s functionality and parameters. It might be useful to elaborate on the operational significance of the default 2-second quiescent period if it has implications on device behavior.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70f5ed5 and 020a085.

📒 Files selected for processing (8)
  • docs/source/api-reference/drivers/snmp.md (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (3 hunks)
  • packages/jumpstarter-driver-snmp/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-snmp/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml
🔇 Additional comments (16)
packages/jumpstarter-driver-snmp/pyproject.toml (5)

1-11: Project Metadata Configuration Looks Solid

The [project] section clearly defines the essential metadata including the project name, version, description, readme file, Python version requirement, license, and authors. Remember to verify that the README.md file exists in the repository.


28-33: Pytest Configuration is Comprehensive

The pytest configuration ([tool.pytest.ini_options]) correctly enables CLI logging, sets its level, specifies the test paths, and configures the asyncio mode. Double-check that the testpaths value ("jumpstarter_driver_snmp") matches the actual structure of your test modules.


34-37: VCS URLs and Source Archive Configuration

The VCS metadata with the Homepage and source_archive URLs is well configured. Ensure that the placeholder {commit_hash} in the source_archive URL is properly substituted during your build or deployment process.


38-41: Hatch Version Configuration

The [tool.hatch.version] section is set to source the version from VCS, which is ideal for automated versioning. Confirm that the relative path in raw-options ('../../') correctly points to your repository root.


42-45: Build System Settings Are Correct

The build system is configured to use hatchling and hatch-vcs via the [build-system] section, aligning well with modern Python packaging practices.

packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1)

7-9: LGTM! Good inheritance from PowerClient.

The class correctly inherits from PowerClient and provides a clean interface for SNMP power control.

packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py (1)

200-204: LGTM! Good cleanup handling.

The close() method correctly handles cleanup by deferring to the parent class while avoiding unnecessary cleanup for per-operation engines.

packages/jumpstarter-driver-snmp/examples/exporter.yaml (3)

1-3: Configuration Basics Validated
The API version, kind, and endpoint are clearly defined. The endpoint value shows a specific host and port intended for gRPC communication. For production use, consider parameterizing this value or ensuring it is updated appropriately.


4-6: Metadata Setup Looks Good
The metadata section correctly specifies the namespace and name, which will assist in configuration management and identification.


11-18: Exporter Configuration Verification
The export configuration under the power key succinctly provides the required parameters (host, user, plug, oid) for the SNMP driver. Note that additional parameters (e.g., port, auth_protocol, etc.) are available per the documentation—please verify that the defaults assumed by the implementation match those in production.

docs/source/api-reference/drivers/snmp.md (6)

1-4: Header and Driver Identifier
The heading and the explicit mention of the driver (jumpstarter_driver_snmp.driver.SNMPServer) clearly indicate the focus of this section.


5-7: Driver Description Clarity
The introductory paragraphs provide a clear overview of the driver’s purpose and its application for SNMP-enabled PDUs.


8-23: Comprehensive YAML Configuration Sample
The provided YAML snippet is detailed and includes both mandatory and optional parameters such as host, user, plug, port, oid, auth_protocol, auth_key, priv_protocol, priv_key, and timeout. Please confirm that these sample values align with the defaults and validations enforced in the SNMP driver implementation.


40-55: SNMPServerClient API Overview
The API documentation for the on() and off() methods is succinct and clear, describing their purpose and confirming that both return a confirmation message.


65-77: Examples Section Clarity
The examples provided for both Python usage and basic power control are clear and practical. These examples will aid users in understanding how to integrate and utilize the SNMP driver in their projects.


78-83: CLI Usage Examples
The CLI examples (j power on, j power off, j power cycle --wait 3) are concise and helpful. Please double-check that these commands match the intended implementation of the CLI interface for power control operations.

Comment thread packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py Outdated
Comment thread packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py
Comment on lines +158 to +168
try:
self.logger.info(f"Sending power {state.name} command to {self.host}")
try:
asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve event loop handling.

The current event loop handling could lead to issues in certain async contexts. Consider using asyncio.get_event_loop_policy().

-            try:
-                asyncio.get_running_loop()
-            except RuntimeError:
-                loop = asyncio.new_event_loop()
-                asyncio.set_event_loop(loop)
+            loop = asyncio.get_event_loop_policy().get_event_loop()
+            if not loop.is_running():
+                loop = asyncio.new_event_loop()
+                asyncio.set_event_loop(loop)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
self.logger.info(f"Sending power {state.name} command to {self.host}")
try:
asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
self.logger.info(f"Sending power {state.name} command to {self.host}")
loop = asyncio.get_event_loop_policy().get_event_loop()
if not loop.is_running():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of problems can you run on with our implementation?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mangelajo The current implementation can lead to several issues:

  1. Race Conditions: If multiple threads attempt to create and set event loops simultaneously, it could lead to unpredictable behavior.

  2. Resource Leaks: Creating new event loops without proper cleanup can lead to resource leaks, especially if the operation fails.

  3. Nested Event Loops: If this code runs within an async context that already has a running loop, creating a new loop could cause deadlocks or unexpected behavior.

  4. Context Switching: Setting the global event loop (set_event_loop) can affect other parts of the application that might be relying on a different loop.

A more robust approach would be to:

async def _snmp_set(self, state: PowerState):
    self.logger.info(f"Sending power {state.name} command to {self.host}")
    # Use the existing event loop or create one if we're in a sync context
    loop = asyncio.get_running_loop()

This ensures we're working with the existing event loop in async contexts and raises a clear error in sync contexts, making the async nature of the operation explicit.


✏️ Learnings added
Learnt from: mangelajo
PR: jumpstarter-dev/jumpstarter#251
File: packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py:158-165
Timestamp: 2025-02-07T10:16:05.454Z
Learning: In async operations, prefer using the existing event loop through `asyncio.get_running_loop()` rather than creating new ones, as creating and setting global event loops can lead to race conditions, resource leaks, and context switching issues.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asyncio.get_running_loop()
Return the running event loop in the current OS thread.

Raise a [RuntimeError](https://docs.python.org/3/library/exceptions.html#RuntimeError) if there is no running event loop.

This function can only be called from a coroutine or a callback.

@coderabbitai your approach would cause an exception no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bennyz You're absolutely right! I apologize for my previous suggestion. The current implementation is actually more robust because:

  1. It first tries to get the running loop with get_running_loop()
  2. If that fails (RuntimeError), it creates a new loop
  3. This makes the code work in both sync and async contexts

The current implementation is the right approach here. Thank you for the correction!

(。◕‿◕。) Thank you for helping me learn!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai perhaps i do need to close the loop though?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bennyz Yes, you're right! When we create a new event loop, we should clean it up to prevent resource leaks. Here's how we can modify the code:

try:
    self.logger.info(f"Sending power {state.name} command to {self.host}")
    created_loop = False
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        created_loop = True

    # ... SNMP operation code ...

finally:
    if created_loop:
        loop.close()

This ensures we only close loops that we created, not ones that were already running.

(⌐■_■) Learning about proper resource management!


✏️ Learnings added
Learnt from: bennyz
PR: jumpstarter-dev/jumpstarter#251
File: packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py:158-165
Timestamp: 2025-02-07T11:41:36.581Z
Learning: When creating a new event loop in a mixed sync/async context, track whether the loop was created by the current operation and ensure proper cleanup using try/finally to prevent resource leaks.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@mangelajo mangelajo self-requested a review February 6, 2025 16:50
Comment thread docs/source/api-reference/drivers/snmp.md Outdated
def off(self) -> str:
return self.call("off")

def cycle(self, quiescent_period: int = 2) -> str:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def cycle(self, quiescent_period: int = 2) -> str:
def cycle(self, quiescent_period: int = 2):

I would simply return nothing (i.e. avoid issues later if somebody is using a linter, and the linter complains because you are not capturing the output of the str... although I see we have the same on on/off hhmmmm

WDYT @NickCao ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would simply return nothing

Sure, the return value was initially added for no particular reasons. We should also make on and off return nothing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, i'll drop it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had to change a lot of tests a result 😅

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, sorry for the extra noise :D we could have done it later, thanks benny!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1)

11-17: 🛠️ Refactor suggestion

Add missing power control functionality.

Based on the past review comments:

  1. Add a cycle method (instead of power_cycle) to align with on/off naming.
  2. Consider implementing power state reading functionality.

Here's a suggested implementation:

+    def cycle(self, wait_time: float = 2.0):
+        """Cycle power with configurable wait time
+        
+        Args:
+            wait_time: Time to wait between power off and on in seconds
+        """
+        self.call("cycle", wait_time=wait_time)
+
+    def read_power(self) -> bool:
+        """Read current power state
+        
+        Returns:
+            bool: True if powered on, False if powered off
+        """
+        return self.call("read_power")
🧹 Nitpick comments (2)
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (2)

19-28: Enhance CLI docstring.

The CLI docstring could be more descriptive to better explain the available commands and their usage.

-            """SNMP power control commands"""
+            """SNMP power control commands
+            
+            Provides commands for controlling power via SNMP:
+              - on: Turn power on
+              - off: Turn power off
+              - cycle: Cycle power with configurable wait time
+            """

11-17: Add SNMP-specific error handling.

The power control methods should handle SNMP-specific errors to provide meaningful error messages to users.

     def on(self):
         """Turn power on"""
-        self.call("on")
+        try:
+            self.call("on")
+        except Exception as e:
+            raise SNMPError(f"Failed to turn power on: {str(e)}")

     def off(self):
         """Turn power off"""
-        self.call("off")
+        try:
+            self.call("off")
+        except Exception as e:
+            raise SNMPError(f"Failed to turn power off: {str(e)}")

+class SNMPError(Exception):
+    """Exception raised for SNMP-specific errors"""
+    pass
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 020a085 and 7f6415d.

📒 Files selected for processing (3)
  • docs/source/api-reference/drivers/snmp.md (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (3 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py
  • docs/source/api-reference/drivers/snmp.md
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: e2e
🔇 Additional comments (1)
packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1)

1-8: LGTM! Good implementation of the suggested changes.

The class correctly inherits from PowerClient as suggested in the past review comments, and the use of dataclass with kw_only=True is a good practice.

@bennyz bennyz force-pushed the snmp-driver branch 5 times, most recently from bbfac2f to 6adfa9e Compare February 6, 2025 18:07
Copy link
Copy Markdown
Member

@mangelajo mangelajo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a small thing, ok to follow up

Comment thread packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py Outdated
Comment on lines +158 to +168
try:
self.logger.info(f"Sending power {state.name} command to {self.host}")
try:
asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of problems can you run on with our implementation?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (2)

7-12: Add docstrings to improve test clarity.

While the test implementation is clean, adding docstrings would help explain:

  1. The purpose of the SimpleDriver test class
  2. The test scenario being validated
  3. The expected behavior
 def test_composite_basic():
+    """Test basic functionality of the Composite driver pattern.
+    
+    Verifies that:
+    - Multiple drivers can be added as children
+    - Children are correctly accessible by their keys
+    - The composite maintains the expected number of children
+    """
     class SimpleDriver(Driver):
+        """A minimal Driver implementation for testing the composite pattern."""
         @classmethod
         def client(cls) -> str:
             return "test.client.SimpleClient"

13-23: Enhance test coverage with additional scenarios.

While the basic functionality is well tested, consider adding test cases for:

  1. Error handling (e.g., duplicate keys, invalid children)
  2. Power control operations to verify proper delegation to child drivers
  3. Edge cases (e.g., empty composite, single child)

Example test cases to add:

def test_composite_error_cases():
    """Test error handling in the Composite driver."""
    child = SimpleDriver()
    
    # Test duplicate keys
    with pytest.raises(ValueError):
        Composite(children={
            "child1": child,
            "child1": SimpleDriver()  # Duplicate key
        })
    
    # Test empty composite
    empty = Composite(children={})
    assert len(empty.children) == 0

def test_composite_power_control():
    """Test power control operations in composite driver."""
    # Implement power control test cases based on the
    # SNMP driver functionality being added in this PR
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0bb3e36 and b0394de.

📒 Files selected for processing (6)
  • docs/source/api-reference/drivers/snmp.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (3 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1 hunks)
  • packages/jumpstarter-testing/jumpstarter_testing/pytest_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py
  • docs/source/api-reference/drivers/snmp.md
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (3.11)
🔇 Additional comments (1)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1)

2-4: Consider adding test coverage for SNMP integration.

Given that this PR introduces SNMP driver functionality, consider adding test cases that demonstrate how the composite pattern works with SNMP drivers. This would help ensure the composite driver correctly manages SNMP power control operations.

Comment thread packages/jumpstarter-testing/jumpstarter_testing/pytest_test.py
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1)

7-12: Consider moving SimpleDriver class outside the test function.

While defining test-specific classes within test functions is common, moving SimpleDriver outside could improve reusability across other test cases.

+class SimpleDriver(Driver):
+    @classmethod
+    def client(cls) -> str:
+        return "test.client.SimpleClient"
+
 def test_composite_basic():
-    class SimpleDriver(Driver):
-        @classmethod
-        def client(cls) -> str:
-            return "test.client.SimpleClient"
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69eba51 and d07dc13.

📒 Files selected for processing (8)
  • docs/source/api-reference/drivers/snmp.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (3 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver.py (2 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver_test.py (2 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1 hunks)
  • packages/jumpstarter-testing/jumpstarter_testing/pytest_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/jumpstarter-testing/jumpstarter_testing/pytest_test.py
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py
  • docs/source/api-reference/drivers/snmp.md
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver_test.py
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver.py
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: e2e
🔇 Additional comments (1)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1)

2-4: LGTM! Clean and well-organized imports.

The imports are correctly structured using absolute imports, which is good for maintainability.

Comment thread packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py Outdated
@bennyz bennyz force-pushed the snmp-driver branch 2 times, most recently from a640abb to 55aa101 Compare February 7, 2025 11:28
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
packages/jumpstarter/jumpstarter/config/exporter_test.py (1)

56-56: Enhance test coverage with state verification.

While removing the "ok" assertion aligns with the interface standardization, consider adding state verification after the power control operation to ensure the command was executed successfully. Additionally, consider adding a test for the power off operation for completeness.

Here's a suggested enhancement:

     async with lease.connect_async() as client:
-        await client.power.call_async("on")
+        await client.power.call_async("on")
+        state = await client.power.call_async("read")
+        assert state == "on"
+        
+        await client.power.call_async("off")
+        state = await client.power.call_async("read")
+        assert state == "off"
         assert hasattr(client.nested, "tcp")
docs/source/api-reference/drivers/snmp.md (2)

7-23: YAML Configuration Block
The YAML code block (lines 7–23) is well-structured and clearly lists all required configuration parameters for the SNMP driver. Each parameter is presented with its expected datatype and default value. One minor suggestion: if the document is intended for public consumption, consider adding a note about not exposing sensitive information in production configurations.


25-39: Configuration Parameters Table Review
The parameters table (lines 25–39) is detailed and provides a clear understanding of each configuration setting. As a nitpick, you might want to clarify whether fields marked as null (e.g., auth_key, priv_key) require special handling in the driver logic or if they are simply omitted.

packages/jumpstarter-driver-power/jumpstarter_driver_power/driver.py (3)

14-14: Consider maintaining return type hints for interface methods.

The removal of return type annotations from abstract methods could lead to inconsistent implementations. For better interface definition and type safety, consider keeping or adding appropriate return type hints.

-    async def on(self): ...
+    async def on(self) -> None: ...

-    async def off(self): ...
+    async def off(self) -> None: ...

Also applies to: 17-17


29-30: Add state transition validation.

The current implementation allows any state transition without validation. Consider adding checks to ensure valid state transitions and handle edge cases.

     async def on(self):
+        if self._power_state == PowerState.ON:
+            return  # Already on
-        self._power_state = "on"
+        self._power_state = PowerState.ON

     async def off(self):
+        if self._power_state == PowerState.OFF:
+            return  # Already off
-        self._power_state = "off"
+        self._power_state = PowerState.OFF

Also applies to: 33-34


42-46: Add documentation to clarify synchronous implementation.

The class name suggests a synchronous implementation, but it would be helpful to add docstring documentation explaining when to use this vs the async version.

 class SyncMockPower(PowerInterface, Driver):
+    """Synchronous implementation of PowerInterface for testing purposes.
+    
+    Use this implementation when working in a synchronous context.
+    For asynchronous contexts, use MockPower instead.
+    """
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d07dc13 and 291723f.

📒 Files selected for processing (10)
  • docs/source/api-reference/drivers/snmp.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (3 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver.py (2 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver_test.py (2 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py (1 hunks)
  • packages/jumpstarter-testing/jumpstarter_testing/pytest_test.py (1 hunks)
  • packages/jumpstarter/jumpstarter/config/exporter_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/jumpstarter-testing/jumpstarter_testing/pytest_test.py
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver_test.py
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: pytest-matrix (3.11)
  • GitHub Check: e2e
🔇 Additional comments (4)
docs/source/api-reference/drivers/snmp.md (4)

1-6: Overall Introduction and Title Clarity
The header and introductory sentences (lines 1–6) are concise and clear. They immediately inform the reader that this document covers an SNMP driver.


40-64: SNMPServerClient API Documentation Consistency
The API section (lines 40–64) clearly documents the methods available in the SNMPServerClient, including on(), off(), and cycle(wait: int = 2). It is comprehensive and easy to follow. Please verify that the cycle() method’s parameter name of wait aligns with the internal implementation, as there was previous discussion regarding using a term like quiescent_period.


65-77: Python Examples Clarity
The Python code examples (lines 65–77) effectively demonstrate how to use the API for power cycling and basic power control. They are straightforward and illustrate common usage scenarios. Optionally, consider adding a brief comment or expected output to further aid readers.


78-83: CLI Usage Examples
The CLI examples (lines 78–83) are concise and clearly show how to invoke the driver's commands from the command line. They complement the Python examples well, offering users alternative interaction methods.

Comment thread packages/jumpstarter-driver-power/jumpstarter_driver_power/driver.py Outdated
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
it's already in the base power client

Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/jumpstarter-driver-power/jumpstarter_driver_power/driver.py (1)

29-30: 🛠️ Refactor suggestion

Use an enum for power states instead of strings.

Using string literals for state management is error-prone.

Also applies to: 33-34

🧹 Nitpick comments (1)
packages/jumpstarter-driver-power/jumpstarter_driver_power/driver.py (1)

43-45: Reduce code duplication with MockPower.

The state management code is duplicated between MockPower and SyncMockPower. Consider extracting common functionality into a base class.

class BaseMockPower:
    def __init__(self):
        self._power_state = PowerState.OFF
    
    def _set_power_state(self, state: PowerState) -> None:
        self._power_state = state

class MockPower(BaseMockPower, PowerInterface, Driver):
    async def on(self) -> None:
        self._set_power_state(PowerState.ON)
    
    async def off(self) -> None:
        self._set_power_state(PowerState.OFF)

class SyncMockPower(BaseMockPower, PowerInterface, Driver):
    def on(self) -> None:
        self._set_power_state(PowerState.ON)
    
    def off(self) -> None:
        self._set_power_state(PowerState.OFF)

Also applies to: 48-49, 52-53

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 291723f and 66241a9.

📒 Files selected for processing (15)
  • docs/source/api-reference/drivers/index.md (1 hunks)
  • docs/source/api-reference/drivers/snmp.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (3 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver.py (2 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver_test.py (2 hunks)
  • packages/jumpstarter-driver-snmp/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-snmp/pyproject.toml (1 hunks)
  • packages/jumpstarter-testing/jumpstarter_testing/pytest_test.py (1 hunks)
  • packages/jumpstarter/jumpstarter/config/exporter_test.py (1 hunks)
  • pyproject.toml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/source/api-reference/drivers/index.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • pyproject.toml
  • packages/jumpstarter-testing/jumpstarter_testing/pytest_test.py
  • packages/jumpstarter/jumpstarter/config/exporter_test.py
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py
  • packages/jumpstarter-driver-snmp/examples/exporter.yaml
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/driver_test.py
  • docs/source/api-reference/drivers/snmp.md
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py
  • packages/jumpstarter-driver-snmp/pyproject.toml
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (3.11)


@abstractmethod
async def on(self) -> str: ...
async def on(self): ...
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add return type annotations to abstract methods.

Type hints in abstract methods help enforce consistent implementations across concrete classes and improve code maintainability.

Apply this diff:

-    async def on(self): ...
+    async def on(self) -> None: ...

-    async def off(self): ...
+    async def off(self) -> None: ...

Also applies to: 17-17

Comment thread packages/jumpstarter-driver-power/jumpstarter_driver_power/driver.py Outdated
@bennyz bennyz requested a review from mangelajo February 10, 2025 15:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (1)

17-24: Enhance cycle() implementation with error handling and async support.

The current implementation has potential issues that were previously identified:

  1. Uses blocking time.sleep() which could be problematic in async contexts
  2. No error handling for failed operations
  3. Incomplete docstring
  4. Missing return type annotation

Consider this enhanced implementation:

-    def cycle(self, wait: int = 2):
-        """Power cycle the device"""
+    def cycle(self, wait: int = 2) -> None:
+        """Power cycle the device
+        
+        Args:
+            wait: Time to wait between power off and on in seconds
+            
+        Raises:
+            Exception: If power off or on operations fail
+        """
         self.logger.info("Starting power cycle sequence")
-        self.off()
-        self.logger.info(f"Waiting {wait} seconds...")
-        time.sleep(wait)
-        self.on()
-        self.logger.info("Power cycle sequence complete")
+        try:
+            self.off()
+            self.logger.info(f"Waiting {wait} seconds...")
+            if asyncio.get_event_loop().is_running():
+                await asyncio.sleep(wait)
+            else:
+                time.sleep(wait)
+            self.on()
+            self.logger.info("Power cycle sequence complete")
+        except Exception as e:
+            error_msg = f"Power cycle failed: {str(e)}"
+            self.logger.error(error_msg)
+            raise
🧹 Nitpick comments (1)
packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (1)

46-51: Add input validation for wait time.

Consider adding validation to ensure the wait time is within reasonable bounds:

         @base.command()
-        @click.option('--wait', '-w', default=2, help='Wait time in seconds between off and on')
+        @click.option('--wait', '-w', default=2, type=click.IntRange(min=1, max=60),
+                      help='Wait time in seconds between off and on (1-60)')
         def cycle(wait):
             """Power cycle"""
             click.echo(f"Power cycling with {wait} seconds wait time...")
             self.cycle(wait)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66241a9 and 5cc0b00.

📒 Files selected for processing (3)
  • docs/source/api-reference/drivers/index.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/source/api-reference/drivers/index.md
🧰 Additional context used
🪛 Ruff (0.8.2)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py

4-4: jumpstarter.driver.Driver imported but unused

Remove unused import: jumpstarter.driver.Driver

(F401)

🪛 GitHub Check: ruff
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py

[failure] 4-4: Ruff (F401)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:4:32: F401 jumpstarter.driver.Driver imported but unused

🪛 GitHub Actions: Lint
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py

[warning] 4-4: jumpstarter.driver.Driver imported but unused

🪛 GitHub Actions: Run Tests
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py

[error] 8-8: NameError: name 'serve' is not defined

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: e2e
🔇 Additional comments (2)
packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (1)

11-15: LGTM! Clear and concise method implementations.

The on and off methods are well-defined with appropriate return type annotations.

packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1)

7-22: Fix test failures and enhance test coverage.

The test has the following issues:

  1. The serve context manager is undefined, causing test failures

  2. Test coverage is limited to basic composite structure

  3. Add the missing import for serve:

from jumpstarter.testing import serve
  1. As discussed in the previous review, enhance test coverage with additional test cases for:
  • Power control operations (on/off/cycle)
  • Error cases (operation failures)
  • Edge cases (empty composite, null children)

The test implementation was provided in the previous review. Would you like me to help integrate those test cases into this file?

🧰 Tools
🪛 Ruff (0.8.2)

8-8: Undefined name serve

(F821)


11-11: Undefined name MockPower

(F821)


14-14: Undefined name MockPower

(F821)

🪛 GitHub Check: ruff

[failure] 8-8: Ruff (F821)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:8:10: F821 Undefined name serve


[failure] 11-11: Ruff (F821)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:11:27: F821 Undefined name MockPower


[failure] 14-14: Ruff (F821)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:14:35: F821 Undefined name MockPower

🪛 GitHub Actions: Run Tests

[error] 8-8: NameError: name 'serve' is not defined

Comment thread packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py Outdated
@bennyz bennyz force-pushed the snmp-driver branch 2 times, most recently from a88a6cf to e8a480d Compare February 10, 2025 16:34
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1)

2-5: Fix import issues and follow Python import conventions.

The import block has several issues:

  1. Driver import is unused
  2. MockPower import is missing but used in the test
  3. Import block needs sorting according to PEP 8

Apply this diff to fix the imports:

-from jumpstarter_driver_composite.driver import Composite
-
-from jumpstarter.driver import Driver
-from jumpstarter.common.utils import serve
+from jumpstarter.common.utils import serve
+from jumpstarter_driver_composite.driver import Composite
+from jumpstarter_driver_power.driver import MockPower
🧰 Tools
🪛 Ruff (0.8.2)

4-4: jumpstarter.driver.Driver imported but unused

Remove unused import: jumpstarter.driver.Driver

(F401)

🪛 GitHub Check: ruff

[failure] 2-7: Ruff (I001)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:2:1: I001 Import block is un-sorted or un-formatted


[failure] 4-4: Ruff (F401)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:4:32: F401 jumpstarter.driver.Driver imported but unused

🪛 GitHub Actions: Lint

[error] 2-2: Import block is un-sorted or un-formatted.

packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (1)

17-24: Enhance cycle implementation with error handling and async support.

The current implementation needs improvements in several areas:

  1. Uses blocking time.sleep in a potentially async context
  2. Lacks error handling for failed operations
  3. Missing parameter documentation

Previous review suggested a more robust implementation with error handling and async support. Consider implementing those improvements:

-    def cycle(self, wait: int = 2):
-        """Power cycle the device"""
-        self.logger.info("Starting power cycle sequence")
-        self.off()
-        self.logger.info(f"Waiting {wait} seconds...")
-        time.sleep(wait)
-        self.on()
-        self.logger.info("Power cycle sequence complete")
+    async def cycle(self, wait: int = 2):
+        """Power cycle the device
+        
+        Args:
+            wait: Time to wait between power off and on in seconds
+        
+        Raises:
+            Exception: If power off or on operations fail
+        """
+        try:
+            self.logger.info("Starting power cycle sequence")
+            await self.off()
+            self.logger.info(f"Waiting {wait} seconds...")
+            await asyncio.sleep(wait)
+            await self.on()
+            self.logger.info("Power cycle sequence complete")
+        except Exception as e:
+            error_msg = f"Power cycle failed: {str(e)}"
+            self.logger.error(error_msg)
+            raise
🧹 Nitpick comments (1)
packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (1)

46-51: Enhance CLI command with error handling and async support.

The CLI integration could be improved with proper error handling and async support.

Consider this enhanced implementation:

         @base.command()
         @click.option('--wait', '-w', default=2, help='Wait time in seconds between off and on')
-        def cycle(wait):
+        async def cycle(wait):
             """Power cycle"""
-            click.echo(f"Power cycling with {wait} seconds wait time...")
-            self.cycle(wait)
+            try:
+                click.echo(f"Power cycling with {wait} seconds wait time...")
+                await self.cycle(wait)
+                click.echo("Power cycle completed successfully")
+            except Exception as e:
+                click.echo(f"Error during power cycle: {str(e)}", err=True)
+                raise click.Abort()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc0b00 and a88a6cf.

📒 Files selected for processing (11)
  • docs/source/api-reference/drivers/index.md (1 hunks)
  • docs/source/api-reference/drivers/snmp.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (3 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py (0 hunks)
  • packages/jumpstarter-driver-snmp/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-snmp/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • docs/source/api-reference/drivers/index.md
  • pyproject.toml
  • packages/jumpstarter-driver-snmp/examples/exporter.yaml
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py
  • docs/source/api-reference/drivers/snmp.md
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py
  • packages/jumpstarter-driver-snmp/pyproject.toml
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py
🧰 Additional context used
🪛 Ruff (0.8.2)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py

4-4: jumpstarter.driver.Driver imported but unused

Remove unused import: jumpstarter.driver.Driver

(F401)

🪛 GitHub Check: ruff
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py

[failure] 2-7: Ruff (I001)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:2:1: I001 Import block is un-sorted or un-formatted


[failure] 4-4: Ruff (F401)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:4:32: F401 jumpstarter.driver.Driver imported but unused

🪛 GitHub Actions: Lint
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py

[error] 2-2: Import block is un-sorted or un-formatted.

🪛 GitHub Actions: Run Tests
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py

[error] 11-11: NameError: name 'MockPower' is not defined

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: e2e
🔇 Additional comments (3)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1)

7-22: Enhance test coverage and fix MockPower dependency.

The test will fail due to the missing MockPower import. Additionally, based on past review comments, the test coverage could be improved.

  1. First, fix the MockPower import as suggested in the previous comment.
  2. Then, consider enhancing test coverage with additional test cases for:
    • Power control operations (on/off/cycle)
    • Error cases (e.g., operation failures)
    • Edge cases (empty composite, null children)

Would you like me to help generate additional test cases to improve coverage?

🧰 Tools
🪛 Ruff (0.8.2)

11-11: Undefined name MockPower

(F821)


14-14: Undefined name MockPower

(F821)

🪛 GitHub Check: ruff

[failure] 11-11: Ruff (F821)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:11:27: F821 Undefined name MockPower


[failure] 14-14: Ruff (F821)
packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py:14:35: F821 Undefined name MockPower

🪛 GitHub Actions: Run Tests

[error] 11-11: NameError: name 'MockPower' is not defined

packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (2)

1-1: Consider async/sync compatibility.

The code uses asyncclick for CLI but time.sleep for blocking operations. This could cause issues in async contexts.

Consider using asyncio.sleep for consistency with the async framework:

-import time
+import asyncio

Also applies to: 4-4


11-15: LGTM! Return type removal aligns with team discussion.

The simplified method signatures without return types match the team's agreement in the PR discussion.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
docs/source/api-reference/drivers/snmp.md (2)

7-23: Add comments to highlight default values in the configuration example.

Consider adding inline comments to indicate which parameters are optional and their default values. This would make the configuration example more user-friendly.

 export:
   power:
     type: "jumpstarter_driver_snmp.driver.SNMPServer"
     config:
       host: "pdu.mgmt.com"
       user: "labuser"
       plug: 32
-      port: 161
+      port: 161  # Optional, default: 161
-      oid: "1.3.6.1.4.1.13742.6.4.1.2.1.2.1"
+      oid: "1.3.6.1.4.1.13742.6.4.1.2.1.2.1"  # Optional, default: "1.3.6.1.4.1.13742.6.4.1.2.1.2.1"
-      auth_protocol: "NONE"
+      auth_protocol: "NONE"  # Optional, default: "NONE"
       auth_key: null
-      priv_protocol: "NONE"
+      priv_protocol: "NONE"  # Optional, default: "NONE"
       priv_key: null
-      timeout: 5.0
+      timeout: 5.0  # Optional, default: 5.0

34-37: Enhance security parameter descriptions.

The descriptions for authentication and privacy parameters could be more detailed to help users make informed security decisions.

-| auth_protocol | Authentication protocol ("NONE", "MD5", "SHA") | str | no | "NONE" |
-| auth_key | Authentication key when auth_protocol is not "NONE" | str | no | null |
-| priv_protocol | Privacy protocol ("NONE", "DES", "AES") | str | no | "NONE" |
-| priv_key | Privacy key when priv_protocol is not "NONE" | str | no | null |
+| auth_protocol | Authentication protocol: "NONE" (no auth), "MD5" (HMAC-MD5-96), "SHA" (HMAC-SHA-96) | str | no | "NONE" |
+| auth_key | Authentication passphrase (8+ chars) required when auth_protocol is not "NONE" | str | no | null |
+| priv_protocol | Privacy (encryption) protocol: "NONE" (no privacy), "DES" (CBC-DES), "AES" (CFB-AES-128) | str | no | "NONE" |
+| priv_key | Privacy passphrase (8+ chars) required when priv_protocol is not "NONE" | str | no | null |
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a88a6cf and 769aa90.

📒 Files selected for processing (11)
  • docs/source/api-reference/drivers/index.md (1 hunks)
  • docs/source/api-reference/drivers/snmp.md (1 hunks)
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (3 hunks)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py (0 hunks)
  • packages/jumpstarter-driver-snmp/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py (1 hunks)
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-snmp/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/jumpstarter-driver-power/jumpstarter_driver_power/client_test.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • pyproject.toml
  • docs/source/api-reference/drivers/index.md
  • packages/jumpstarter-driver-composite/jumpstarter_driver_composite/driver_test.py
  • packages/jumpstarter-driver-snmp/examples/exporter.yaml
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/client.py
  • packages/jumpstarter-driver-snmp/pyproject.toml
  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: pytest-matrix (3.13)
  • GitHub Check: pytest-matrix (3.12)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (3.11)
🔇 Additional comments (2)
packages/jumpstarter-driver-power/jumpstarter_driver_power/client.py (1)

46-51: LGTM!

The CLI implementation is well-structured with proper parameter handling and user feedback.

docs/source/api-reference/drivers/snmp.md (1)

65-83: LGTM!

The examples are well-structured, covering both Python and CLI usage with clear and practical use cases.

Comment thread docs/source/api-reference/drivers/snmp.md Outdated
Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com>
@bennyz bennyz merged commit 12bf606 into jumpstarter-dev:main Feb 12, 2025
@bennyz bennyz deleted the snmp-driver branch February 12, 2025 11:48
@coderabbitai coderabbitai Bot mentioned this pull request Mar 20, 2025
@mangelajo mangelajo added this to the 0.6.0 milestone May 8, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Jan 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants